This notebook dives deeper into using CellAssign with marker gene sets derived from the PanglaoDB database. Previously we had compared using marker gene sets from a single organ or tissue type using two different databases, PanglaoDB or CellMarker2.0. We liked the organization of PanglaoDB, other than the missing cell ontology IDs, so want to see how CellAssign performs when using marker gene sets obtained by combining cell types across organs.

Here we run CellAssign using two different gene sets and compare the results to the previous CellAssign results using only cell types found in the muscle/connective tissue and to the submitter annotations.

  • combined refers to a reference gene set that contains all cell types found in muscle, connective tissue, and the immune system.
  • combined_custom refers to a reference gene set that contains all cell types found in muscle and connective tissue and only Monocytes from the immune system. This is because the only overlap between the submitter annotations and the cells in the immune system found in PanglaoDB are Monocytes.
  • muscle_only refers to a reference gene set that contains only cell types found in muscle and connective tissue. The predictions from using this reference gene set were obtained by running CellAssign in the cell-assign-sarcoma.Rmd notebook.

Set Up

library(SingleCellExperiment)
Loading required package: SummarizedExperiment
Loading required package: MatrixGenerics
Loading required package: matrixStats

Attaching package: 'MatrixGenerics'
The following objects are masked from 'package:matrixStats':

    colAlls, colAnyNAs, colAnys, colAvgsPerRowSet, colCollapse, colCounts, colCummaxs, colCummins, colCumprods, colCumsums, colDiffs, colIQRDiffs, colIQRs, colLogSumExps,
    colMadDiffs, colMads, colMaxs, colMeans2, colMedians, colMins, colOrderStats, colProds, colQuantiles, colRanges, colRanks, colSdDiffs, colSds, colSums2, colTabulates,
    colVarDiffs, colVars, colWeightedMads, colWeightedMeans, colWeightedMedians, colWeightedSds, colWeightedVars, rowAlls, rowAnyNAs, rowAnys, rowAvgsPerColSet, rowCollapse,
    rowCounts, rowCummaxs, rowCummins, rowCumprods, rowCumsums, rowDiffs, rowIQRDiffs, rowIQRs, rowLogSumExps, rowMadDiffs, rowMads, rowMaxs, rowMeans2, rowMedians, rowMins,
    rowOrderStats, rowProds, rowQuantiles, rowRanges, rowRanks, rowSdDiffs, rowSds, rowSums2, rowTabulates, rowVarDiffs, rowVars, rowWeightedMads, rowWeightedMeans,
    rowWeightedMedians, rowWeightedSds, rowWeightedVars
Loading required package: GenomicRanges
Loading required package: stats4
Loading required package: BiocGenerics

Attaching package: 'BiocGenerics'
The following objects are masked from 'package:stats':

    IQR, mad, sd, var, xtabs
The following objects are masked from 'package:base':

    anyDuplicated, append, as.data.frame, basename, cbind, colnames, dirname, do.call, duplicated, eval, evalq, Filter, Find, get, grep, grepl, intersect, is.unsorted, lapply,
    Map, mapply, match, mget, order, paste, pmax, pmax.int, pmin, pmin.int, Position, rank, rbind, Reduce, rownames, sapply, setdiff, sort, table, tapply, union, unique, unsplit,
    which.max, which.min
Loading required package: S4Vectors

Attaching package: 'S4Vectors'
The following objects are masked from 'package:base':

    expand.grid, I, unname
Loading required package: IRanges
Loading required package: GenomeInfoDb
Loading required package: Biobase
Welcome to Bioconductor

    Vignettes contain introductory material; view with 'browseVignettes()'. To cite Bioconductor, see 'citation("Biobase")', and for packages 'citation("pkgname")'.

Attaching package: 'Biobase'
The following object is masked from 'package:MatrixGenerics':

    rowMedians
The following objects are masked from 'package:matrixStats':

    anyMissing, rowMedians
library(ggplot2)

# source helper functions
function_file <- file.path("..", "utils", "cellassign-helper-functions.R")
source(function_file)
# Set up paths 

# build path to annotated sce file 
processed_data_dir <-file.path("..", "data") 
processed_sce_file <- glue::glue("{params$library_id}_processed_celltype.rds")
local_processed_sce_path <- file.path(processed_data_dir, params$sample_id, processed_sce_file)

# anndata output
anndata_dir <- file.path("..", "data", "anndata")
anndata_file <- glue::glue("{params$library_id}_anndata.hdf5")
anndata_file <- file.path(anndata_dir, anndata_file)

# reference files for cell marker
ref_dir <- file.path("..", "references")
panglao_file <- file.path(ref_dir, "PanglaoDB_markers_27_Mar_2020.tsv")

# matrix files 
panglao_comb_mtx_file <- file.path(ref_dir, "panglao_combined_mtx.csv")
panglao_custom_mtx_file <- file.path(ref_dir, "panglao_combined-custom_mtx.csv")
panglao_muscle_only_mtx_file <- file.path(ref_dir, "panglao_muscle_mtx.csv")

# predictions output
cellassign_outs_dir <- "cellassign_results"

# cellassign predictions
combined_prediction_file <- file.path(
  cellassign_outs_dir, 
  glue::glue("{params$library_id}_panglao_combined.tsv")
)
muscle_only_prediction_file <- file.path(
  cellassign_outs_dir, 
  glue::glue("{params$library_id}_panglao_muscle.tsv")
)
custom_prediction_file <- file.path(
  cellassign_outs_dir, 
  glue::glue("{params$library_id}_panglao_combined-custom.tsv")
)
# if the file isn't present, grab it from aws
if(!file.exists(local_processed_sce_path)){
  
  filename <- basename(local_processed_sce_path)
  local_data_dir <- file.path("..", "data", params$sample_id)
  s3_dir <- "s3://sc-data-integration/scpca/celltype_sce"
  fs::dir_create(local_data_dir)
  
  sync_call <- glue::glue('op run -- aws s3 sync {s3_dir} {local_data_dir} --exclude "*" --include "{filename}"')
  system(sync_call, ignore.stdout = TRUE) 
  
}


# read in annotated sce 
processed_sce <- readr::read_rds(local_processed_sce_path)
if(!file.exists(anndata_file)){
 # write out anndata 
  scpcaTools::sce_to_anndata(processed_sce, anndata_file = anndata_file) 
}

Prep marker gene sets for CellAssign

# read in panglao db 
panglao_df <- readr::read_tsv(panglao_file)
Rows: 8286 Columns: 14
── Column specification ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: "\t"
chr (8): species, official gene symbol, cell type, nicknames, product description, gene type, germ layer, organ
dbl (6): ubiquitousness index, canonical marker, sensitivity_human, sensitivity_mouse, specificity_human, specificity_mouse

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
muscle_cells <- c("Connective tissue", "Smooth muscle", "Skeletal muscle")

# grab all muscle related organs + all immune
panglao_tissue_immune_combined <- panglao_df |> 
  dplyr::filter(organ %in% c(muscle_cells, "Immune system"))

panglao_tissue_immune_combined |>
  dplyr::count(`cell type`, `organ`)
# grab all muscle related organs + monocytes only
custom_marker_genes <- panglao_df |> 
  dplyr::filter(organ %in% muscle_cells |
                `cell type` == "Monocytes")

custom_marker_genes |>
  dplyr::count(`cell type`, `organ`)
# list of matrix files to create
mtx_files <- c(panglao_comb_mtx_file,
               panglao_custom_mtx_file)

# if any of them don't exist, make them 
if(!any(file.exists(mtx_files))){
 
  # create rowdata df
  rowdata_df <- rowData(processed_sce) |>
    as.data.frame() |>
    tibble::rownames_to_column("ensembl_id") |>
    dplyr::select(ensembl_id, gene_symbol)
  
  # create list of marker gene sets
  all_marker_genes <- list(panglao_tissue_immune_combined,
                           custom_marker_genes)
  names(all_marker_genes) <- c("combined", "combined_custom")
  
  # get binary mtx for combined tissues 
  binary_matrix_list <- all_marker_genes |> 
    purrr::map(\(gene_list) build_binary_mtx(marker_genes_df = gene_list,
                                             celltype_column = 'cell type',
                                             gene_id_column = 'official gene symbol',
                                             rowdata_df = rowdata_df))
  
  # export mtx 
  purrr::walk2(binary_matrix_list, mtx_files,
               \(binary_mtx, file)
               readr::write_csv(binary_mtx, file))
  
}
# list of output prediction files 
prediction_files <- c(combined_prediction_file,
                      custom_prediction_file) 
# run cell assign with muscle ref
cellassign_calls <- purrr::map2(prediction_files, mtx_files,
                                \(predictions, marker_genes) {
                                  glue::glue("python ../scripts/cell-assign.py \
                                              --input_anndata '{anndata_file}' \
                                              --output_predictions '{predictions}' \
                                              --reference '{marker_genes}'")
                                })

system(cellassign_call)

CellAssign Results

# add muscle only to prediction file list 
prediction_files <- c(prediction_files, muscle_only_prediction_file)
names(prediction_files) <- c("combined", "combined_custom", "muscle_only")

# read in prediction scores 
all_predictions <- prediction_files |>
  purrr::map(readr::read_tsv)
Rows: 5948 Columns: 41
── Column specification ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: "\t"
chr  (1): barcode
dbl (40): Adipocyte progenitor cells, Adipocytes, Airway smooth muscle cells, B cells, B cells memory, B cells naive, Basophils, Chondrocytes, Dendritic cells, Eosinophils, Fibroblasts, Gamma del...

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
Rows: 5948 Columns: 17
── Column specification ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: "\t"
chr  (1): barcode
dbl (16): Adipocyte progenitor cells, Adipocytes, Airway smooth muscle cells, Chondrocytes, Fibroblasts, Monocytes, Myoblasts, Myocytes, Myoepithelial cells, Myofibroblasts, Pulmonary vascular sm...

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
Rows: 5948 Columns: 16
── Column specification ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: "\t"
chr  (1): barcode
dbl (15): Adipocyte progenitor cells, Adipocytes, Airway smooth muscle cells, Chondrocytes, Fibroblasts, Myoblasts, Myocytes, Myoepithelial cells, Myofibroblasts, Pulmonary vascular smooth muscle...

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
# get final cell type assignments 
all_assignments <- purrr::map(all_predictions,
                              get_celltype_assignments)
# print table of assignments
all_assignments |>
  purrr::map(\(celltype_assignments){
    
    # print out table of assignments
    celltype_assignments |> 
      dplyr::count(celltype) |>
      dplyr::arrange(desc(n))
    
  })
$combined
# A tibble: 29 × 2
   celltype                       n
   <chr>                      <int>
 1 other                       4946
 2 Fibroblasts                  227
 3 Megakaryocytes               162
 4 T memory cells               133
 5 Dendritic cells              106
 6 Monocytes                     99
 7 Myocytes                      76
 8 Airway smooth muscle cells    31
 9 B cells                       29
10 Plasma cells                  19
# … with 19 more rows
# ℹ Use `print(n = ...)` to see more rows

$combined_custom
# A tibble: 14 × 2
   celltype                         n
   <chr>                        <int>
 1 other                         4914
 2 Monocytes                      535
 3 Fibroblasts                    237
 4 Myocytes                       107
 5 Stromal cells                   47
 6 Airway smooth muscle cells      30
 7 Vascular smooth muscle cells    19
 8 Myoblasts                       16
 9 Satellite cells                 12
10 Myoepithelial cells             11
11 Myofibroblasts                   9
12 Smooth muscle cells              6
13 Adipocyte progenitor cells       3
14 Chondrocytes                     2

$muscle_only
# A tibble: 14 × 2
   celltype                         n
   <chr>                        <int>
 1 other                         5261
 2 Fibroblasts                    284
 3 Myocytes                       161
 4 Stromal cells                  135
 5 Airway smooth muscle cells      28
 6 Satellite cells                 17
 7 Myoepithelial cells             16
 8 Myofibroblasts                  11
 9 Vascular smooth muscle cells     9
10 Smooth muscle cells              8
11 Adipocyte progenitor cells       6
12 Chondrocytes                     6
13 Myoblasts                        5
14 Adipocytes                       1
# heatmap of prediction scores 
all_predictions |>
  purrr::iwalk(\(prediction_mtx, organ_type){
    
    prediction_mtx |>
      dplyr::select(-barcode) |> 
      as.matrix() |> 
      pheatmap::pheatmap(show_rownames = FALSE,
                         main = organ_type)
    
  })

The main conclusion after looking at this is that regardless of the marker gene set, most cells get classified as “other”

Comparison between marker gene sets

Here we directly compare the annotations between different marker gene sets and the submitter annotations.

# add celltypes to processed sce 
processed_sce$combined_assignments <- all_assignments$combined$celltype
processed_sce$combined_custom_assignments <- all_assignments$combined_custom$celltype
processed_sce$muscle_only_assignments <- all_assignments$muscle_only$celltype
# extract coldata for easy plotting
coldata_df <- colData(processed_sce) |>
  as.data.frame() |> 
  dplyr::select(barcode, celltype, combined_assignments, combined_custom_assignments, muscle_only_assignments) |>
  # classify cells as tumor or normal
  dplyr::mutate(cell_category = dplyr::if_else(stringr::str_detect(celltype, "Tumor"), "Tumor", celltype),
                # remove sub states of myoblast/mesoderm/myocyte
                broad_celltype = stringr::str_remove(celltype, "-\\w$"))
compare_refs_heatmap(original_assignment = coldata_df$combined_assignments,
                     cell_assign = coldata_df$muscle_only_assignments,
                     title = "Combined vs. Muscle only")

compare_refs_heatmap(original_assignment = coldata_df$combined_assignments,
                     cell_assign = coldata_df$broad_celltype,
                     title = "Combined vs. Submitter annotations")

compare_refs_heatmap(original_assignment = coldata_df$combined_custom_assignments,
                     cell_assign = coldata_df$muscle_only_assignments,
                     title = "Combined-custom vs. Muscle only")

compare_refs_heatmap(original_assignment = coldata_df$combined_custom_assignments,
                     cell_assign = coldata_df$broad_celltype,
                     title = "Combined-custom vs. Submitter annotations")

compare_refs_heatmap(original_assignment = coldata_df$muscle_only_assignments,
                     cell_assign = coldata_df$broad_celltype,
                     title = "Muscle only vs. Submitter annotations")

Compare to clustering

This section will compare the various cell type annotations to the cluster assignments. For simplicity, we will use louvain-jaccard graph-based clustering with the default nearest neighbors parameter of 10. We will look at UMAPs and heatmaps of the cluster assignments and cell type assignments. We expect that any assigned cell types should be somewhat correlated to the clusters, in particular for cell types that are less frequent. This means that a single cell type is probably less likely to be spread across multiple clusters.

# cluster sce object
pcs <- reducedDim(processed_sce, "PCA")
clusters <- bluster::clusterRows(pcs,
                                 bluster::NNGraphParam(
                                   cluster.fun = "louvain", 
                                   type = "jaccard"
                                 ))
top_n_labels <- 10

# pull out the cell type columns that we want to plot
umap_labels <- c("cluster_assignment", 
                 "broad_celltype",
                 "combined_assignments",
                 "combined_custom_assignments",
                 "muscle_only_assignments")

umap_df <- coldata_df |> 
  # add clusters and UMAP embeddings to coldata
  dplyr::mutate(
    cluster_assignment = clusters,
    UMAP_1 = reducedDim(processed_sce, "UMAP")[,1],
    UMAP_2 = reducedDim(processed_sce, "UMAP")[,2]
  ) |>
  # combine all the cell type assignments into one column
  tidyr::pivot_longer(cols = all_of(umap_labels),
                      names_to = "label_type",
                      values_to = "cell_label") |>
  # relabel other to distinguish from "other" added by forcats
  # Cells categorized by other in CellAssign become Not assigned 
  dplyr::mutate(cell_label = dplyr::if_else(cell_label == "other",
                                            "Not assigned",
                                            cell_label)) |>
  dplyr::group_split(label_type) |>
  # only grab the top cell types for each cell type assignment method
  # all other cell types will get grouped as "other" 
  purrr::map(\(df) 
             dplyr::mutate(df, 
                           top_labels = forcats::fct_lump_n(
                             df$cell_label, 
                             n = top_n_labels
                             ))) |>
  dplyr::bind_rows()
# create a separate umap for each of the cluster/ cell type assignments 
# only plot the top cell types otherwise it gets overwhelming with colors
purrr::map(umap_labels,
           \(label){
             
             plotting_df <- umap_df |>
               dplyr::filter(label_type %in% label)

             ggplot(plotting_df, aes(x = UMAP_1, y = UMAP_2, color = top_labels)) +
               geom_point(size = 0.2, alpha = 0.5) +
               guides(color = guide_legend(override.aes = list(size = 3))) +
               labs(color = label, title = label) +
               theme_bw()
               
           })
[[1]]


[[2]]


[[3]]


[[4]]


[[5]]

# heatmaps comparing clusters to cell type assignments
heatmap_labels <- umap_labels[umap_labels != "cluster_assignment"]
purrr::map(heatmap_labels,
           \(label) compare_refs_heatmap(coldata_df[, label],
                                         clusters,
                                         cluster_cols = FALSE,
                                         cluster_rows = TRUE,
                                         title = glue::glue("{label} across clusters")))
[[1]]


[[2]]


[[3]]


[[4]]

It looks like the smaller immune cell populations (e.g., the T memory cells and Dendritic Cells in the combined annotation) are spread throughout the same cluster that contains cells that are “Not assigned”. These correlate with tumor cells in the submitter annotations. I would have maybe expected to see those cells in their own cluster or separate from the clusters containing tumor cells.

Conclusions

  • Regardless of which combination of cell types we choose from PanglaoDB most of the cells get categorized as “other” for this sample. One thing to note is that when using SingleR we are able to assign cells to muscle cells and skeletal muscle cells. Although the assignments from SingleR (see singler-rms-comparison.Rmd) don’t completely line up with the submitter annotations, identifying cells as muscle cells rather than labeling most of them as “other” does seem more informative to me.
  • The more marker genes you have the more time CellAssign takes. We already knew this, but this was a new record. It took ~ 2 hours to run the immune + connective tissue with 8 cpus.
  • I don’t think adding in the immune system really helped that much. It just made it take longer and then assigned cells to immune cells that were not previously identified by submitters in our dataset. Maybe we don’t need to use multiple organs?

Session Info

sessionInfo()
R version 4.2.1 (2022-06-23)
Platform: x86_64-apple-darwin17.0 (64-bit)
Running under: macOS Monterey 12.5.1

Matrix products: default
LAPACK: /Library/Frameworks/R.framework/Versions/4.2/Resources/lib/libRlapack.dylib

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

attached base packages:
[1] stats4    stats     graphics  grDevices datasets  utils     methods   base     

other attached packages:
 [1] ggplot2_3.3.6               SingleCellExperiment_1.18.0 SummarizedExperiment_1.26.1 Biobase_2.56.0              GenomicRanges_1.48.0        GenomeInfoDb_1.32.3        
 [7] IRanges_2.30.0              S4Vectors_0.34.0            BiocGenerics_0.42.0         MatrixGenerics_1.8.1        matrixStats_0.62.0         

loaded via a namespace (and not attached):
  [1] circlize_0.4.15               AnnotationHub_3.4.0           BiocFileCache_2.4.0           igraph_1.3.4                  splines_4.2.1                 BiocParallel_1.30.3          
  [7] scater_1.24.0                 digest_0.6.29                 foreach_1.5.2                 htmltools_0.5.3               viridis_0.6.2                 fansi_1.0.3                  
 [13] magrittr_2.0.3                memoise_2.0.1                 ScaledMatrix_1.4.0            cluster_2.1.3                 doParallel_1.0.17             limma_3.52.2                 
 [19] tzdb_0.3.0                    ontologyPlot_1.6              openxlsx_4.2.5.2              miQC_1.4.0                    ComplexHeatmap_2.12.1         Biostrings_2.64.0            
 [25] readr_2.1.2                   vroom_1.5.7                   colorspace_2.0-3              blob_1.2.3                    rappdirs_0.3.3                ggrepel_0.9.1                
 [31] xfun_0.32                     dplyr_1.0.9                   jsonlite_1.8.0                crayon_1.5.1                  RCurl_1.98-1.8                graph_1.74.0                 
 [37] iterators_1.0.14              glue_1.6.2                    gtable_0.3.0                  zlibbioc_1.42.0               XVector_0.36.0                GetoptLong_1.0.5             
 [43] DelayedArray_0.22.0           BiocSingular_1.12.0           Rgraphviz_2.40.0              shape_1.4.6                   scales_1.2.0                  pheatmap_1.0.12              
 [49] edgeR_3.38.4                  DBI_1.1.3                     Rcpp_1.0.9                    viridisLite_0.4.0             xtable_1.8-4                  clue_0.3-64                  
 [55] dqrng_0.3.0                   bit_4.0.4                     rsvd_1.0.5                    DT_0.26                       metapod_1.4.0                 htmlwidgets_1.5.4            
 [61] httr_1.4.3                    ontologyIndex_2.11            RColorBrewer_1.1-3            modeltools_0.2-23             ellipsis_0.3.2                farver_2.1.1                 
 [67] flexmix_2.3-18                pkgconfig_2.0.3               scuttle_1.6.2                 nnet_7.3-17                   sass_0.4.2                    dbplyr_2.2.1                 
 [73] locfit_1.5-9.6                utf8_1.2.2                    labeling_0.4.2                tidyselect_1.1.2              rlang_1.0.4                   later_1.3.0                  
 [79] AnnotationDbi_1.58.0          munsell_0.5.0                 BiocVersion_3.15.2            tools_4.2.1                   cachem_1.0.6                  cli_3.3.0                    
 [85] generics_0.1.3                RSQLite_2.2.15                evaluate_0.16                 stringr_1.4.0                 fastmap_1.1.0                 yaml_2.3.5                   
 [91] knitr_1.39                    bit64_4.0.5                   fs_1.5.2                      zip_2.3.0                     purrr_0.3.4                   KEGGREST_1.36.3              
 [97] sparseMatrixStats_1.8.0       mime_0.12                     scran_1.24.0                  compiler_4.2.1                rstudioapi_0.13               beeswarm_0.4.0               
[103] filelock_1.0.2                curl_4.3.2                    png_0.1-7                     interactiveDisplayBase_1.34.0 paintmap_1.0                  statmod_1.4.37               
[109] tibble_3.1.8                  bslib_0.4.0                   stringi_1.7.8                 highr_0.9                     scpcaTools_0.1.8              forcats_0.5.2                
[115] lattice_0.20-45               bluster_1.6.0                 Matrix_1.4-1                  vctrs_0.4.1                   pillar_1.8.0                  lifecycle_1.0.1              
[121] BiocManager_1.30.18           jquerylib_0.1.4               GlobalOptions_0.1.2           BiocNeighbors_1.14.0          bitops_1.0-7                  irlba_2.3.5                  
[127] httpuv_1.6.5                  R6_2.5.1                      promises_1.2.0.1              renv_0.15.5                   gridExtra_2.3                 vipor_0.4.5                  
[133] ontoProc_1.18.0               codetools_0.2-18              assertthat_0.2.1              rjson_0.2.21                  withr_2.5.0                   GenomeInfoDbData_1.2.8       
[139] parallel_4.2.1                hms_1.1.1                     grid_4.2.1                    beachmat_2.12.0               tidyr_1.2.0                   rmarkdown_2.14               
[145] DelayedMatrixStats_1.18.0     shiny_1.7.2                   ggbeeswarm_0.6.0             
LS0tCnRpdGxlOiAiQ2VsbEFzc2lnbjogQW5ub3RhdGluZyBSaGFiZG9teW9zYXJjb21hIHdpdGggY29tYmluZWQgbWFya2VyIGdlbmUgc2V0cyIKb3V0cHV0OiAKICBodG1sX25vdGVib29rOgogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6IHRydWUKICAgIGNvZGVfZm9sZGluZzogImhpZGUiCnBhcmFtczoKICBsaWJyYXJ5X2lkOiAiU0NQQ0wwMDA0ODgiCiAgc2FtcGxlX2lkOiAiU0NQQ1MwMDAyNjIiCi0tLQoKVGhpcyBub3RlYm9vayBkaXZlcyBkZWVwZXIgaW50byB1c2luZyBgQ2VsbEFzc2lnbmAgd2l0aCBtYXJrZXIgZ2VuZSBzZXRzIGRlcml2ZWQgZnJvbSB0aGUgW2BQYW5nbGFvREJgIGRhdGFiYXNlXShodHRwczovL3BhbmdsYW9kYi5zZS9pbmRleC5odG1sKS4gClByZXZpb3VzbHkgd2UgaGFkIGNvbXBhcmVkIHVzaW5nIG1hcmtlciBnZW5lIHNldHMgZnJvbSBhIHNpbmdsZSBvcmdhbiBvciB0aXNzdWUgdHlwZSB1c2luZyB0d28gZGlmZmVyZW50IGRhdGFiYXNlcywgYFBhbmdsYW9EQmAgb3IgYENlbGxNYXJrZXIyLjBgLiAKV2UgbGlrZWQgdGhlIG9yZ2FuaXphdGlvbiBvZiBgUGFuZ2xhb0RCYCwgb3RoZXIgdGhhbiB0aGUgbWlzc2luZyBjZWxsIG9udG9sb2d5IElEcywgc28gd2FudCB0byBzZWUgaG93IGBDZWxsQXNzaWduYCBwZXJmb3JtcyB3aGVuIHVzaW5nIG1hcmtlciBnZW5lIHNldHMgb2J0YWluZWQgYnkgY29tYmluaW5nIGNlbGwgdHlwZXMgYWNyb3NzIG9yZ2Fucy4gCgpIZXJlIHdlIHJ1biBgQ2VsbEFzc2lnbmAgdXNpbmcgdHdvIGRpZmZlcmVudCBnZW5lIHNldHMgYW5kIGNvbXBhcmUgdGhlIHJlc3VsdHMgdG8gdGhlIHByZXZpb3VzIGBDZWxsQXNzaWduYCByZXN1bHRzIHVzaW5nIG9ubHkgY2VsbCB0eXBlcyBmb3VuZCBpbiB0aGUgbXVzY2xlL2Nvbm5lY3RpdmUgdGlzc3VlIGFuZCB0byB0aGUgc3VibWl0dGVyIGFubm90YXRpb25zLiAKCi0gYGNvbWJpbmVkYCByZWZlcnMgdG8gYSByZWZlcmVuY2UgZ2VuZSBzZXQgdGhhdCBjb250YWlucyBhbGwgY2VsbCB0eXBlcyBmb3VuZCBpbiBtdXNjbGUsIGNvbm5lY3RpdmUgdGlzc3VlLCBhbmQgdGhlIGltbXVuZSBzeXN0ZW0uIAotIGBjb21iaW5lZF9jdXN0b21gIHJlZmVycyB0byBhIHJlZmVyZW5jZSBnZW5lIHNldCB0aGF0IGNvbnRhaW5zIGFsbCBjZWxsIHR5cGVzIGZvdW5kIGluIG11c2NsZSBhbmQgY29ubmVjdGl2ZSB0aXNzdWUgYW5kIG9ubHkgTW9ub2N5dGVzIGZyb20gdGhlIGltbXVuZSBzeXN0ZW0uClRoaXMgaXMgYmVjYXVzZSB0aGUgb25seSBvdmVybGFwIGJldHdlZW4gdGhlIHN1Ym1pdHRlciBhbm5vdGF0aW9ucyBhbmQgdGhlIGNlbGxzIGluIHRoZSBpbW11bmUgc3lzdGVtIGZvdW5kIGluIGBQYW5nbGFvREJgIGFyZSBNb25vY3l0ZXMuIAotIGBtdXNjbGVfb25seWAgcmVmZXJzIHRvIGEgcmVmZXJlbmNlIGdlbmUgc2V0IHRoYXQgY29udGFpbnMgb25seSBjZWxsIHR5cGVzIGZvdW5kIGluIG11c2NsZSBhbmQgY29ubmVjdGl2ZSB0aXNzdWUuIApUaGUgcHJlZGljdGlvbnMgZnJvbSB1c2luZyB0aGlzIHJlZmVyZW5jZSBnZW5lIHNldCB3ZXJlIG9idGFpbmVkIGJ5IHJ1bm5pbmcgYENlbGxBc3NpZ25gIGluIHRoZSBgY2VsbC1hc3NpZ24tc2FyY29tYS5SbWRgIG5vdGVib29rLiAKCiMjIFNldCBVcAoKYGBge3J9CmxpYnJhcnkoU2luZ2xlQ2VsbEV4cGVyaW1lbnQpCmxpYnJhcnkoZ2dwbG90MikKCiMgc291cmNlIGhlbHBlciBmdW5jdGlvbnMKZnVuY3Rpb25fZmlsZSA8LSBmaWxlLnBhdGgoIi4uIiwgInV0aWxzIiwgImNlbGxhc3NpZ24taGVscGVyLWZ1bmN0aW9ucy5SIikKc291cmNlKGZ1bmN0aW9uX2ZpbGUpCmBgYAoKYGBge3J9CiMgU2V0IHVwIHBhdGhzIAoKIyBidWlsZCBwYXRoIHRvIGFubm90YXRlZCBzY2UgZmlsZSAKcHJvY2Vzc2VkX2RhdGFfZGlyIDwtZmlsZS5wYXRoKCIuLiIsICJkYXRhIikgCnByb2Nlc3NlZF9zY2VfZmlsZSA8LSBnbHVlOjpnbHVlKCJ7cGFyYW1zJGxpYnJhcnlfaWR9X3Byb2Nlc3NlZF9jZWxsdHlwZS5yZHMiKQpsb2NhbF9wcm9jZXNzZWRfc2NlX3BhdGggPC0gZmlsZS5wYXRoKHByb2Nlc3NlZF9kYXRhX2RpciwgcGFyYW1zJHNhbXBsZV9pZCwgcHJvY2Vzc2VkX3NjZV9maWxlKQoKIyBhbm5kYXRhIG91dHB1dAphbm5kYXRhX2RpciA8LSBmaWxlLnBhdGgoIi4uIiwgImRhdGEiLCAiYW5uZGF0YSIpCmFubmRhdGFfZmlsZSA8LSBnbHVlOjpnbHVlKCJ7cGFyYW1zJGxpYnJhcnlfaWR9X2FubmRhdGEuaGRmNSIpCmFubmRhdGFfZmlsZSA8LSBmaWxlLnBhdGgoYW5uZGF0YV9kaXIsIGFubmRhdGFfZmlsZSkKCiMgcmVmZXJlbmNlIGZpbGVzIGZvciBjZWxsIG1hcmtlcgpyZWZfZGlyIDwtIGZpbGUucGF0aCgiLi4iLCAicmVmZXJlbmNlcyIpCnBhbmdsYW9fZmlsZSA8LSBmaWxlLnBhdGgocmVmX2RpciwgIlBhbmdsYW9EQl9tYXJrZXJzXzI3X01hcl8yMDIwLnRzdiIpCgojIG1hdHJpeCBmaWxlcyAKcGFuZ2xhb19jb21iX210eF9maWxlIDwtIGZpbGUucGF0aChyZWZfZGlyLCAicGFuZ2xhb19jb21iaW5lZF9tdHguY3N2IikKcGFuZ2xhb19jdXN0b21fbXR4X2ZpbGUgPC0gZmlsZS5wYXRoKHJlZl9kaXIsICJwYW5nbGFvX2NvbWJpbmVkLWN1c3RvbV9tdHguY3N2IikKcGFuZ2xhb19tdXNjbGVfb25seV9tdHhfZmlsZSA8LSBmaWxlLnBhdGgocmVmX2RpciwgInBhbmdsYW9fbXVzY2xlX210eC5jc3YiKQoKIyBwcmVkaWN0aW9ucyBvdXRwdXQKY2VsbGFzc2lnbl9vdXRzX2RpciA8LSAiY2VsbGFzc2lnbl9yZXN1bHRzIgoKIyBjZWxsYXNzaWduIHByZWRpY3Rpb25zCmNvbWJpbmVkX3ByZWRpY3Rpb25fZmlsZSA8LSBmaWxlLnBhdGgoCiAgY2VsbGFzc2lnbl9vdXRzX2RpciwgCiAgZ2x1ZTo6Z2x1ZSgie3BhcmFtcyRsaWJyYXJ5X2lkfV9wYW5nbGFvX2NvbWJpbmVkLnRzdiIpCikKbXVzY2xlX29ubHlfcHJlZGljdGlvbl9maWxlIDwtIGZpbGUucGF0aCgKICBjZWxsYXNzaWduX291dHNfZGlyLCAKICBnbHVlOjpnbHVlKCJ7cGFyYW1zJGxpYnJhcnlfaWR9X3BhbmdsYW9fbXVzY2xlLnRzdiIpCikKY3VzdG9tX3ByZWRpY3Rpb25fZmlsZSA8LSBmaWxlLnBhdGgoCiAgY2VsbGFzc2lnbl9vdXRzX2RpciwgCiAgZ2x1ZTo6Z2x1ZSgie3BhcmFtcyRsaWJyYXJ5X2lkfV9wYW5nbGFvX2NvbWJpbmVkLWN1c3RvbS50c3YiKQopCgpgYGAKCgpgYGB7cn0KIyBpZiB0aGUgZmlsZSBpc24ndCBwcmVzZW50LCBncmFiIGl0IGZyb20gYXdzCmlmKCFmaWxlLmV4aXN0cyhsb2NhbF9wcm9jZXNzZWRfc2NlX3BhdGgpKXsKICAKICBmaWxlbmFtZSA8LSBiYXNlbmFtZShsb2NhbF9wcm9jZXNzZWRfc2NlX3BhdGgpCiAgbG9jYWxfZGF0YV9kaXIgPC0gZmlsZS5wYXRoKCIuLiIsICJkYXRhIiwgcGFyYW1zJHNhbXBsZV9pZCkKICBzM19kaXIgPC0gInMzOi8vc2MtZGF0YS1pbnRlZ3JhdGlvbi9zY3BjYS9jZWxsdHlwZV9zY2UiCiAgZnM6OmRpcl9jcmVhdGUobG9jYWxfZGF0YV9kaXIpCiAgCiAgc3luY19jYWxsIDwtIGdsdWU6OmdsdWUoJ29wIHJ1biAtLSBhd3MgczMgc3luYyB7czNfZGlyfSB7bG9jYWxfZGF0YV9kaXJ9IC0tZXhjbHVkZSAiKiIgLS1pbmNsdWRlICJ7ZmlsZW5hbWV9IicpCiAgc3lzdGVtKHN5bmNfY2FsbCwgaWdub3JlLnN0ZG91dCA9IFRSVUUpIAogIAp9CgoKIyByZWFkIGluIGFubm90YXRlZCBzY2UgCnByb2Nlc3NlZF9zY2UgPC0gcmVhZHI6OnJlYWRfcmRzKGxvY2FsX3Byb2Nlc3NlZF9zY2VfcGF0aCkKYGBgCgpgYGB7cn0KaWYoIWZpbGUuZXhpc3RzKGFubmRhdGFfZmlsZSkpewogIyB3cml0ZSBvdXQgYW5uZGF0YSAKICBzY3BjYVRvb2xzOjpzY2VfdG9fYW5uZGF0YShwcm9jZXNzZWRfc2NlLCBhbm5kYXRhX2ZpbGUgPSBhbm5kYXRhX2ZpbGUpIAp9CmBgYAoKIyMgUHJlcCBtYXJrZXIgZ2VuZSBzZXRzIGZvciBDZWxsQXNzaWduCgpgYGB7cn0KIyByZWFkIGluIHBhbmdsYW8gZGIgCnBhbmdsYW9fZGYgPC0gcmVhZHI6OnJlYWRfdHN2KHBhbmdsYW9fZmlsZSkKYGBgCmBgYHtyfQptdXNjbGVfY2VsbHMgPC0gYygiQ29ubmVjdGl2ZSB0aXNzdWUiLCAiU21vb3RoIG11c2NsZSIsICJTa2VsZXRhbCBtdXNjbGUiKQoKIyBncmFiIGFsbCBtdXNjbGUgcmVsYXRlZCBvcmdhbnMgKyBhbGwgaW1tdW5lCnBhbmdsYW9fdGlzc3VlX2ltbXVuZV9jb21iaW5lZCA8LSBwYW5nbGFvX2RmIHw+IAogIGRwbHlyOjpmaWx0ZXIob3JnYW4gJWluJSBjKG11c2NsZV9jZWxscywgIkltbXVuZSBzeXN0ZW0iKSkKCnBhbmdsYW9fdGlzc3VlX2ltbXVuZV9jb21iaW5lZCB8PgogIGRwbHlyOjpjb3VudChgY2VsbCB0eXBlYCwgYG9yZ2FuYCkKYGBgCgpgYGB7cn0KIyBncmFiIGFsbCBtdXNjbGUgcmVsYXRlZCBvcmdhbnMgKyBtb25vY3l0ZXMgb25seQpjdXN0b21fbWFya2VyX2dlbmVzIDwtIHBhbmdsYW9fZGYgfD4gCiAgZHBseXI6OmZpbHRlcihvcmdhbiAlaW4lIG11c2NsZV9jZWxscyB8CiAgICAgICAgICAgICAgICBgY2VsbCB0eXBlYCA9PSAiTW9ub2N5dGVzIikKCmN1c3RvbV9tYXJrZXJfZ2VuZXMgfD4KICBkcGx5cjo6Y291bnQoYGNlbGwgdHlwZWAsIGBvcmdhbmApCmBgYAoKYGBge3J9CiMgbGlzdCBvZiBtYXRyaXggZmlsZXMgdG8gY3JlYXRlCm10eF9maWxlcyA8LSBjKHBhbmdsYW9fY29tYl9tdHhfZmlsZSwKICAgICAgICAgICAgICAgcGFuZ2xhb19jdXN0b21fbXR4X2ZpbGUpCgojIGlmIGFueSBvZiB0aGVtIGRvbid0IGV4aXN0LCBtYWtlIHRoZW0gCmlmKCFhbnkoZmlsZS5leGlzdHMobXR4X2ZpbGVzKSkpewogCiAgIyBjcmVhdGUgcm93ZGF0YSBkZgogIHJvd2RhdGFfZGYgPC0gcm93RGF0YShwcm9jZXNzZWRfc2NlKSB8PgogICAgYXMuZGF0YS5mcmFtZSgpIHw+CiAgICB0aWJibGU6OnJvd25hbWVzX3RvX2NvbHVtbigiZW5zZW1ibF9pZCIpIHw+CiAgICBkcGx5cjo6c2VsZWN0KGVuc2VtYmxfaWQsIGdlbmVfc3ltYm9sKQogIAogICMgY3JlYXRlIGxpc3Qgb2YgbWFya2VyIGdlbmUgc2V0cwogIGFsbF9tYXJrZXJfZ2VuZXMgPC0gbGlzdChwYW5nbGFvX3Rpc3N1ZV9pbW11bmVfY29tYmluZWQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGN1c3RvbV9tYXJrZXJfZ2VuZXMpCiAgbmFtZXMoYWxsX21hcmtlcl9nZW5lcykgPC0gYygiY29tYmluZWQiLCAiY29tYmluZWRfY3VzdG9tIikKICAKICAjIGdldCBiaW5hcnkgbXR4IGZvciBjb21iaW5lZCB0aXNzdWVzIAogIGJpbmFyeV9tYXRyaXhfbGlzdCA8LSBhbGxfbWFya2VyX2dlbmVzIHw+IAogICAgcHVycnI6Om1hcChcKGdlbmVfbGlzdCkgYnVpbGRfYmluYXJ5X210eChtYXJrZXJfZ2VuZXNfZGYgPSBnZW5lX2xpc3QsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNlbGx0eXBlX2NvbHVtbiA9ICdjZWxsIHR5cGUnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBnZW5lX2lkX2NvbHVtbiA9ICdvZmZpY2lhbCBnZW5lIHN5bWJvbCcsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJvd2RhdGFfZGYgPSByb3dkYXRhX2RmKSkKICAKICAjIGV4cG9ydCBtdHggCiAgcHVycnI6OndhbGsyKGJpbmFyeV9tYXRyaXhfbGlzdCwgbXR4X2ZpbGVzLAogICAgICAgICAgICAgICBcKGJpbmFyeV9tdHgsIGZpbGUpCiAgICAgICAgICAgICAgIHJlYWRyOjp3cml0ZV9jc3YoYmluYXJ5X210eCwgZmlsZSkpCiAgCn0KYGBgCgpgYGB7cn0KIyBsaXN0IG9mIG91dHB1dCBwcmVkaWN0aW9uIGZpbGVzIApwcmVkaWN0aW9uX2ZpbGVzIDwtIGMoY29tYmluZWRfcHJlZGljdGlvbl9maWxlLAogICAgICAgICAgICAgICAgICAgICAgY3VzdG9tX3ByZWRpY3Rpb25fZmlsZSkgCmBgYAoKCmBgYHtyLCAgZXZhbCA9IEZBTFNFfQojIHJ1biBjZWxsIGFzc2lnbiB3aXRoIG11c2NsZSByZWYKY2VsbGFzc2lnbl9jYWxscyA8LSBwdXJycjo6bWFwMihwcmVkaWN0aW9uX2ZpbGVzLCBtdHhfZmlsZXMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXChwcmVkaWN0aW9ucywgbWFya2VyX2dlbmVzKSB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBnbHVlOjpnbHVlKCJweXRob24gLi4vc2NyaXB0cy9jZWxsLWFzc2lnbi5weSBcCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAtLWlucHV0X2FubmRhdGEgJ3thbm5kYXRhX2ZpbGV9JyBcCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAtLW91dHB1dF9wcmVkaWN0aW9ucyAne3ByZWRpY3Rpb25zfScgXAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLS1yZWZlcmVuY2UgJ3ttYXJrZXJfZ2VuZXN9JyIpCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfSkKCnN5c3RlbShjZWxsYXNzaWduX2NhbGwpCmBgYAoKIyMgQ2VsbEFzc2lnbiBSZXN1bHRzCgpgYGB7cn0KIyBhZGQgbXVzY2xlIG9ubHkgdG8gcHJlZGljdGlvbiBmaWxlIGxpc3QgCnByZWRpY3Rpb25fZmlsZXMgPC0gYyhwcmVkaWN0aW9uX2ZpbGVzLCBtdXNjbGVfb25seV9wcmVkaWN0aW9uX2ZpbGUpCm5hbWVzKHByZWRpY3Rpb25fZmlsZXMpIDwtIGMoImNvbWJpbmVkIiwgImNvbWJpbmVkX2N1c3RvbSIsICJtdXNjbGVfb25seSIpCgojIHJlYWQgaW4gcHJlZGljdGlvbiBzY29yZXMgCmFsbF9wcmVkaWN0aW9ucyA8LSBwcmVkaWN0aW9uX2ZpbGVzIHw+CiAgcHVycnI6Om1hcChyZWFkcjo6cmVhZF90c3YpCmBgYAoKYGBge3J9CiMgZ2V0IGZpbmFsIGNlbGwgdHlwZSBhc3NpZ25tZW50cyAKYWxsX2Fzc2lnbm1lbnRzIDwtIHB1cnJyOjptYXAoYWxsX3ByZWRpY3Rpb25zLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBnZXRfY2VsbHR5cGVfYXNzaWdubWVudHMpCmBgYAoKCmBgYHtyfQojIHByaW50IHRhYmxlIG9mIGFzc2lnbm1lbnRzCmFsbF9hc3NpZ25tZW50cyB8PgogIHB1cnJyOjptYXAoXChjZWxsdHlwZV9hc3NpZ25tZW50cyl7CiAgICAKICAgICMgcHJpbnQgb3V0IHRhYmxlIG9mIGFzc2lnbm1lbnRzCiAgICBjZWxsdHlwZV9hc3NpZ25tZW50cyB8PiAKICAgICAgZHBseXI6OmNvdW50KGNlbGx0eXBlKSB8PgogICAgICBkcGx5cjo6YXJyYW5nZShkZXNjKG4pKQogICAgCiAgfSkKYGBgCgpgYGB7ciwgZmlnLmhlaWdodD01LCBmaWcud2lkdGg9MTB9CiMgaGVhdG1hcCBvZiBwcmVkaWN0aW9uIHNjb3JlcyAKYWxsX3ByZWRpY3Rpb25zIHw+CiAgcHVycnI6Oml3YWxrKFwocHJlZGljdGlvbl9tdHgsIG9yZ2FuX3R5cGUpewogICAgCiAgICBwcmVkaWN0aW9uX210eCB8PgogICAgICBkcGx5cjo6c2VsZWN0KC1iYXJjb2RlKSB8PiAKICAgICAgYXMubWF0cml4KCkgfD4gCiAgICAgIHBoZWF0bWFwOjpwaGVhdG1hcChzaG93X3Jvd25hbWVzID0gRkFMU0UsCiAgICAgICAgICAgICAgICAgICAgICAgICBtYWluID0gb3JnYW5fdHlwZSkKICAgIAogIH0pCmBgYAoKVGhlIG1haW4gY29uY2x1c2lvbiBhZnRlciBsb29raW5nIGF0IHRoaXMgaXMgdGhhdCByZWdhcmRsZXNzIG9mIHRoZSBtYXJrZXIgZ2VuZSBzZXQsIG1vc3QgY2VsbHMgZ2V0IGNsYXNzaWZpZWQgYXMgIm90aGVyIgoKIyMgQ29tcGFyaXNvbiBiZXR3ZWVuIG1hcmtlciBnZW5lIHNldHMKCkhlcmUgd2UgZGlyZWN0bHkgY29tcGFyZSB0aGUgYW5ub3RhdGlvbnMgYmV0d2VlbiBkaWZmZXJlbnQgbWFya2VyIGdlbmUgc2V0cyBhbmQgdGhlIHN1Ym1pdHRlciBhbm5vdGF0aW9ucy4gCgpgYGB7cn0KIyBhZGQgY2VsbHR5cGVzIHRvIHByb2Nlc3NlZCBzY2UgCnByb2Nlc3NlZF9zY2UkY29tYmluZWRfYXNzaWdubWVudHMgPC0gYWxsX2Fzc2lnbm1lbnRzJGNvbWJpbmVkJGNlbGx0eXBlCnByb2Nlc3NlZF9zY2UkY29tYmluZWRfY3VzdG9tX2Fzc2lnbm1lbnRzIDwtIGFsbF9hc3NpZ25tZW50cyRjb21iaW5lZF9jdXN0b20kY2VsbHR5cGUKcHJvY2Vzc2VkX3NjZSRtdXNjbGVfb25seV9hc3NpZ25tZW50cyA8LSBhbGxfYXNzaWdubWVudHMkbXVzY2xlX29ubHkkY2VsbHR5cGUKYGBgCgoKYGBge3J9CiMgZXh0cmFjdCBjb2xkYXRhIGZvciBlYXN5IHBsb3R0aW5nCmNvbGRhdGFfZGYgPC0gY29sRGF0YShwcm9jZXNzZWRfc2NlKSB8PgogIGFzLmRhdGEuZnJhbWUoKSB8PiAKICBkcGx5cjo6c2VsZWN0KGJhcmNvZGUsIGNlbGx0eXBlLCBjb21iaW5lZF9hc3NpZ25tZW50cywgY29tYmluZWRfY3VzdG9tX2Fzc2lnbm1lbnRzLCBtdXNjbGVfb25seV9hc3NpZ25tZW50cykgfD4KICAjIGNsYXNzaWZ5IGNlbGxzIGFzIHR1bW9yIG9yIG5vcm1hbAogIGRwbHlyOjptdXRhdGUoY2VsbF9jYXRlZ29yeSA9IGRwbHlyOjppZl9lbHNlKHN0cmluZ3I6OnN0cl9kZXRlY3QoY2VsbHR5cGUsICJUdW1vciIpLCAiVHVtb3IiLCBjZWxsdHlwZSksCiAgICAgICAgICAgICAgICAjIHJlbW92ZSBzdWIgc3RhdGVzIG9mIG15b2JsYXN0L21lc29kZXJtL215b2N5dGUKICAgICAgICAgICAgICAgIGJyb2FkX2NlbGx0eXBlID0gc3RyaW5ncjo6c3RyX3JlbW92ZShjZWxsdHlwZSwgIi1cXHckIikpCmBgYAoKYGBge3IsIGZpZy53aWR0aD03fQpjb21wYXJlX3JlZnNfaGVhdG1hcChvcmlnaW5hbF9hc3NpZ25tZW50ID0gY29sZGF0YV9kZiRjb21iaW5lZF9hc3NpZ25tZW50cywKICAgICAgICAgICAgICAgICAgICAgY2VsbF9hc3NpZ24gPSBjb2xkYXRhX2RmJG11c2NsZV9vbmx5X2Fzc2lnbm1lbnRzLAogICAgICAgICAgICAgICAgICAgICB0aXRsZSA9ICJDb21iaW5lZCB2cy4gTXVzY2xlIG9ubHkiKQpgYGAKCmBgYHtyLCBmaWcud2lkdGg9N30KY29tcGFyZV9yZWZzX2hlYXRtYXAob3JpZ2luYWxfYXNzaWdubWVudCA9IGNvbGRhdGFfZGYkY29tYmluZWRfYXNzaWdubWVudHMsCiAgICAgICAgICAgICAgICAgICAgIGNlbGxfYXNzaWduID0gY29sZGF0YV9kZiRicm9hZF9jZWxsdHlwZSwKICAgICAgICAgICAgICAgICAgICAgdGl0bGUgPSAiQ29tYmluZWQgdnMuIFN1Ym1pdHRlciBhbm5vdGF0aW9ucyIpCmBgYAoKYGBge3IsIGZpZy53aWR0aD03fQpjb21wYXJlX3JlZnNfaGVhdG1hcChvcmlnaW5hbF9hc3NpZ25tZW50ID0gY29sZGF0YV9kZiRjb21iaW5lZF9jdXN0b21fYXNzaWdubWVudHMsCiAgICAgICAgICAgICAgICAgICAgIGNlbGxfYXNzaWduID0gY29sZGF0YV9kZiRtdXNjbGVfb25seV9hc3NpZ25tZW50cywKICAgICAgICAgICAgICAgICAgICAgdGl0bGUgPSAiQ29tYmluZWQtY3VzdG9tIHZzLiBNdXNjbGUgb25seSIpCmBgYAoKCmBgYHtyLCBmaWcud2lkdGg9N30KY29tcGFyZV9yZWZzX2hlYXRtYXAob3JpZ2luYWxfYXNzaWdubWVudCA9IGNvbGRhdGFfZGYkY29tYmluZWRfY3VzdG9tX2Fzc2lnbm1lbnRzLAogICAgICAgICAgICAgICAgICAgICBjZWxsX2Fzc2lnbiA9IGNvbGRhdGFfZGYkYnJvYWRfY2VsbHR5cGUsCiAgICAgICAgICAgICAgICAgICAgIHRpdGxlID0gIkNvbWJpbmVkLWN1c3RvbSB2cy4gU3VibWl0dGVyIGFubm90YXRpb25zIikKYGBgCgpgYGB7ciwgZmlnLndpZHRoPTd9CmNvbXBhcmVfcmVmc19oZWF0bWFwKG9yaWdpbmFsX2Fzc2lnbm1lbnQgPSBjb2xkYXRhX2RmJG11c2NsZV9vbmx5X2Fzc2lnbm1lbnRzLAogICAgICAgICAgICAgICAgICAgICBjZWxsX2Fzc2lnbiA9IGNvbGRhdGFfZGYkYnJvYWRfY2VsbHR5cGUsCiAgICAgICAgICAgICAgICAgICAgIHRpdGxlID0gIk11c2NsZSBvbmx5IHZzLiBTdWJtaXR0ZXIgYW5ub3RhdGlvbnMiKQpgYGAKCiMjIENvbXBhcmUgdG8gY2x1c3RlcmluZyAKClRoaXMgc2VjdGlvbiB3aWxsIGNvbXBhcmUgdGhlIHZhcmlvdXMgY2VsbCB0eXBlIGFubm90YXRpb25zIHRvIHRoZSBjbHVzdGVyIGFzc2lnbm1lbnRzLiAKRm9yIHNpbXBsaWNpdHksIHdlIHdpbGwgdXNlIGxvdXZhaW4tamFjY2FyZCBncmFwaC1iYXNlZCBjbHVzdGVyaW5nIHdpdGggdGhlIGRlZmF1bHQgbmVhcmVzdCBuZWlnaGJvcnMgcGFyYW1ldGVyIG9mIDEwLiAKV2Ugd2lsbCBsb29rIGF0IFVNQVBzIGFuZCBoZWF0bWFwcyBvZiB0aGUgY2x1c3RlciBhc3NpZ25tZW50cyBhbmQgY2VsbCB0eXBlIGFzc2lnbm1lbnRzLgpXZSBleHBlY3QgdGhhdCBhbnkgYXNzaWduZWQgY2VsbCB0eXBlcyBzaG91bGQgYmUgc29tZXdoYXQgY29ycmVsYXRlZCB0byB0aGUgY2x1c3RlcnMsIGluIHBhcnRpY3VsYXIgZm9yIGNlbGwgdHlwZXMgdGhhdCBhcmUgbGVzcyBmcmVxdWVudC4KVGhpcyBtZWFucyB0aGF0IGEgc2luZ2xlIGNlbGwgdHlwZSBpcyBwcm9iYWJseSBsZXNzIGxpa2VseSB0byBiZSBzcHJlYWQgYWNyb3NzIG11bHRpcGxlIGNsdXN0ZXJzLgoKYGBge3J9CiMgY2x1c3RlciBzY2Ugb2JqZWN0CnBjcyA8LSByZWR1Y2VkRGltKHByb2Nlc3NlZF9zY2UsICJQQ0EiKQpjbHVzdGVycyA8LSBibHVzdGVyOjpjbHVzdGVyUm93cyhwY3MsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJsdXN0ZXI6Ok5OR3JhcGhQYXJhbSgKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjbHVzdGVyLmZ1biA9ICJsb3V2YWluIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHlwZSA9ICJqYWNjYXJkIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApKQpgYGAKCmBgYHtyfQp0b3Bfbl9sYWJlbHMgPC0gMTAKCiMgcHVsbCBvdXQgdGhlIGNlbGwgdHlwZSBjb2x1bW5zIHRoYXQgd2Ugd2FudCB0byBwbG90CnVtYXBfbGFiZWxzIDwtIGMoImNsdXN0ZXJfYXNzaWdubWVudCIsIAogICAgICAgICAgICAgICAgICJicm9hZF9jZWxsdHlwZSIsCiAgICAgICAgICAgICAgICAgImNvbWJpbmVkX2Fzc2lnbm1lbnRzIiwKICAgICAgICAgICAgICAgICAiY29tYmluZWRfY3VzdG9tX2Fzc2lnbm1lbnRzIiwKICAgICAgICAgICAgICAgICAibXVzY2xlX29ubHlfYXNzaWdubWVudHMiKQoKdW1hcF9kZiA8LSBjb2xkYXRhX2RmIHw+IAogICMgYWRkIGNsdXN0ZXJzIGFuZCBVTUFQIGVtYmVkZGluZ3MgdG8gY29sZGF0YQogIGRwbHlyOjptdXRhdGUoCiAgICBjbHVzdGVyX2Fzc2lnbm1lbnQgPSBjbHVzdGVycywKICAgIFVNQVBfMSA9IHJlZHVjZWREaW0ocHJvY2Vzc2VkX3NjZSwgIlVNQVAiKVssMV0sCiAgICBVTUFQXzIgPSByZWR1Y2VkRGltKHByb2Nlc3NlZF9zY2UsICJVTUFQIilbLDJdCiAgKSB8PgogICMgY29tYmluZSBhbGwgdGhlIGNlbGwgdHlwZSBhc3NpZ25tZW50cyBpbnRvIG9uZSBjb2x1bW4KICB0aWR5cjo6cGl2b3RfbG9uZ2VyKGNvbHMgPSBhbGxfb2YodW1hcF9sYWJlbHMpLAogICAgICAgICAgICAgICAgICAgICAgbmFtZXNfdG8gPSAibGFiZWxfdHlwZSIsCiAgICAgICAgICAgICAgICAgICAgICB2YWx1ZXNfdG8gPSAiY2VsbF9sYWJlbCIpIHw+CiAgIyByZWxhYmVsIG90aGVyIHRvIGRpc3Rpbmd1aXNoIGZyb20gIm90aGVyIiBhZGRlZCBieSBmb3JjYXRzCiAgIyBDZWxscyBjYXRlZ29yaXplZCBieSBvdGhlciBpbiBDZWxsQXNzaWduIGJlY29tZSBOb3QgYXNzaWduZWQgCiAgZHBseXI6Om11dGF0ZShjZWxsX2xhYmVsID0gZHBseXI6OmlmX2Vsc2UoY2VsbF9sYWJlbCA9PSAib3RoZXIiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJOb3QgYXNzaWduZWQiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNlbGxfbGFiZWwpKSB8PgogIGRwbHlyOjpncm91cF9zcGxpdChsYWJlbF90eXBlKSB8PgogICMgb25seSBncmFiIHRoZSB0b3AgY2VsbCB0eXBlcyBmb3IgZWFjaCBjZWxsIHR5cGUgYXNzaWdubWVudCBtZXRob2QKICAjIGFsbCBvdGhlciBjZWxsIHR5cGVzIHdpbGwgZ2V0IGdyb3VwZWQgYXMgIm90aGVyIiAKICBwdXJycjo6bWFwKFwoZGYpIAogICAgICAgICAgICAgZHBseXI6Om11dGF0ZShkZiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHRvcF9sYWJlbHMgPSBmb3JjYXRzOjpmY3RfbHVtcF9uKAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRmJGNlbGxfbGFiZWwsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG4gPSB0b3Bfbl9sYWJlbHMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICApKSkgfD4KICBkcGx5cjo6YmluZF9yb3dzKCkKYGBgCgoKYGBge3J9CiMgY3JlYXRlIGEgc2VwYXJhdGUgdW1hcCBmb3IgZWFjaCBvZiB0aGUgY2x1c3Rlci8gY2VsbCB0eXBlIGFzc2lnbm1lbnRzIAojIG9ubHkgcGxvdCB0aGUgdG9wIGNlbGwgdHlwZXMgb3RoZXJ3aXNlIGl0IGdldHMgb3ZlcndoZWxtaW5nIHdpdGggY29sb3JzCnB1cnJyOjptYXAodW1hcF9sYWJlbHMsCiAgICAgICAgICAgXChsYWJlbCl7CiAgICAgICAgICAgICAKICAgICAgICAgICAgIHBsb3R0aW5nX2RmIDwtIHVtYXBfZGYgfD4KICAgICAgICAgICAgICAgZHBseXI6OmZpbHRlcihsYWJlbF90eXBlICVpbiUgbGFiZWwpCgogICAgICAgICAgICAgZ2dwbG90KHBsb3R0aW5nX2RmLCBhZXMoeCA9IFVNQVBfMSwgeSA9IFVNQVBfMiwgY29sb3IgPSB0b3BfbGFiZWxzKSkgKwogICAgICAgICAgICAgICBnZW9tX3BvaW50KHNpemUgPSAwLjIsIGFscGhhID0gMC41KSArCiAgICAgICAgICAgICAgIGd1aWRlcyhjb2xvciA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KHNpemUgPSAzKSkpICsKICAgICAgICAgICAgICAgbGFicyhjb2xvciA9IGxhYmVsLCB0aXRsZSA9IGxhYmVsKSArCiAgICAgICAgICAgICAgIHRoZW1lX2J3KCkKICAgICAgICAgICAgICAgCiAgICAgICAgICAgfSkKYGBgCgpgYGB7cn0KIyBoZWF0bWFwcyBjb21wYXJpbmcgY2x1c3RlcnMgdG8gY2VsbCB0eXBlIGFzc2lnbm1lbnRzCmhlYXRtYXBfbGFiZWxzIDwtIHVtYXBfbGFiZWxzW3VtYXBfbGFiZWxzICE9ICJjbHVzdGVyX2Fzc2lnbm1lbnQiXQpwdXJycjo6bWFwKGhlYXRtYXBfbGFiZWxzLAogICAgICAgICAgIFwobGFiZWwpIGNvbXBhcmVfcmVmc19oZWF0bWFwKGNvbGRhdGFfZGZbLCBsYWJlbF0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2x1c3RlcnMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2x1c3Rlcl9jb2xzID0gRkFMU0UsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2x1c3Rlcl9yb3dzID0gVFJVRSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aXRsZSA9IGdsdWU6OmdsdWUoIntsYWJlbH0gYWNyb3NzIGNsdXN0ZXJzIikpKQpgYGAKSXQgbG9va3MgbGlrZSB0aGUgc21hbGxlciBpbW11bmUgY2VsbCBwb3B1bGF0aW9ucyAoZS5nLiwgdGhlIFQgbWVtb3J5IGNlbGxzIGFuZCBEZW5kcml0aWMgQ2VsbHMgaW4gdGhlIGNvbWJpbmVkIGFubm90YXRpb24pIGFyZSBzcHJlYWQgdGhyb3VnaG91dCB0aGUgc2FtZSBjbHVzdGVyIHRoYXQgY29udGFpbnMgY2VsbHMgdGhhdCBhcmUgIk5vdCBhc3NpZ25lZCIuClRoZXNlIGNvcnJlbGF0ZSB3aXRoIHR1bW9yIGNlbGxzIGluIHRoZSBzdWJtaXR0ZXIgYW5ub3RhdGlvbnMuCkkgd291bGQgaGF2ZSBtYXliZSBleHBlY3RlZCB0byBzZWUgdGhvc2UgY2VsbHMgaW4gdGhlaXIgb3duIGNsdXN0ZXIgb3Igc2VwYXJhdGUgZnJvbSB0aGUgY2x1c3RlcnMgY29udGFpbmluZyB0dW1vciBjZWxscy4KCiMjIENvbmNsdXNpb25zCgotIFJlZ2FyZGxlc3Mgb2Ygd2hpY2ggY29tYmluYXRpb24gb2YgY2VsbCB0eXBlcyB3ZSBjaG9vc2UgZnJvbSBgUGFuZ2xhb0RCYCBtb3N0IG9mIHRoZSBjZWxscyBnZXQgY2F0ZWdvcml6ZWQgYXMgIm90aGVyIiBmb3IgdGhpcyBzYW1wbGUuIApPbmUgdGhpbmcgdG8gbm90ZSBpcyB0aGF0IHdoZW4gdXNpbmcgYFNpbmdsZVJgIHdlIGFyZSBhYmxlIHRvIGFzc2lnbiBjZWxscyB0byBgbXVzY2xlIGNlbGxzYCBhbmQgYHNrZWxldGFsIG11c2NsZSBjZWxsc2AuIApBbHRob3VnaCB0aGUgYXNzaWdubWVudHMgZnJvbSBgU2luZ2xlUmAgKHNlZSBgc2luZ2xlci1ybXMtY29tcGFyaXNvbi5SbWRgKSBkb24ndCBjb21wbGV0ZWx5IGxpbmUgdXAgd2l0aCB0aGUgc3VibWl0dGVyIGFubm90YXRpb25zLCBpZGVudGlmeWluZyBjZWxscyBhcyBtdXNjbGUgY2VsbHMgcmF0aGVyIHRoYW4gbGFiZWxpbmcgbW9zdCBvZiB0aGVtIGFzICJvdGhlciIgZG9lcyBzZWVtIG1vcmUgaW5mb3JtYXRpdmUgdG8gbWUuIAotIFRoZSBtb3JlIG1hcmtlciBnZW5lcyB5b3UgaGF2ZSB0aGUgbW9yZSB0aW1lIGBDZWxsQXNzaWduYCB0YWtlcy4gCldlIGFscmVhZHkga25ldyB0aGlzLCBidXQgdGhpcyB3YXMgYSBuZXcgcmVjb3JkLiAKSXQgdG9vayB+IDIgaG91cnMgdG8gcnVuIHRoZSBpbW11bmUgKyBjb25uZWN0aXZlIHRpc3N1ZSB3aXRoIDggY3B1cy4gCi0gSSBkb24ndCB0aGluayBhZGRpbmcgaW4gdGhlIGltbXVuZSBzeXN0ZW0gcmVhbGx5IGhlbHBlZCB0aGF0IG11Y2guCkl0IGp1c3QgbWFkZSBpdCB0YWtlIGxvbmdlciBhbmQgdGhlbiBhc3NpZ25lZCBjZWxscyB0byBpbW11bmUgY2VsbHMgdGhhdCB3ZXJlIG5vdCBwcmV2aW91c2x5IGlkZW50aWZpZWQgYnkgc3VibWl0dGVycyBpbiBvdXIgZGF0YXNldC4KTWF5YmUgd2UgZG9uJ3QgbmVlZCB0byB1c2UgbXVsdGlwbGUgb3JnYW5zPyAKCgojIyBTZXNzaW9uIEluZm8KCmBgYHtyfQpzZXNzaW9uSW5mbygpCmBgYAoK